#pragma once

/**
Basically a wrapper around ordinary char array to make read/write perations alot simpler. The
reason that this class is a template is to make allocation of byte buffers happen on the stack
and not dynamically, resulting in better performance. All read/write operations throw 
OverflowException upon read or write past the end of the buffer. 
*/
class ByteBuffer{
public:
	class Exception{};
	///exception that is thrown upon bad function parameters
	class BadArgumentException:public Exception{};
	///exception that is thrown upon overflows during read write operations
	class OverflowException:public Exception{};

	///Constructor :P
	ByteBuffer(size_t size){
		data=new char[size];
		_size=size;
		bytePos=bitPos=bitCount=0;
	}
	~ByteBuffer(){
		delete [] data;
	}
	///returns pointer to the data within
	const char *c_ptr(){
		return data;
	}

	///puts the 'cursor' to zero
	void	rewind(){
		bytePos=0;
		bitPos=0;
	}
	///sets cursor to a given byte and bit inside the buffer (0<=bit<8, 0<=byte<_size)
	void	seek(unsigned int byte, unsigned int bit){
		if (byte > _size-1||bit>7)
			throw OverflowException();

		bitPos = bit;
		bytePos = byte;
	}
	///returns length of the buffer in bytes
	size_t	length(){
		return (bitCount%8)?bitCount/8:bitCount/8+1;
	}
	///returns capacity of this buffer
	size_t		size(){return _size;}
	///writes bits from 'value' that have true bits in 'bits' field 
	void	writeBits(int value, unsigned int bits){
		unsigned int		write, count;
		if (bits < 1 || bits > 32)
			throw BadArgumentException();
		if (bytePos*8+bitPos + bits > (_size << 3))
			throw OverflowException();

		bitCount+=bits;

		// Write the bits
		while (bits){
			if (bitPos == 0){
				if (bits >= 8){
					write = value & 255;

					this->data[bytePos++] = write;

					value >>= 8;
					bits -= 8;

					continue;
				}

				this->data[bytePos++] = 0;
			}

			count = 8 - bitPos;
			if (count > bits)
				count = bits;

			write = value & (BIT(count) - 1);

			this->data[bytePos - 1] |= write << bitPos;
			bitPos = (bitPos + count) & 7;

			value >>= count;
			bits -= count;
		}
	}
	///writes a boolean (ususally takes just one bit)
	void	writeBool(bool value){
		writeBits((value != false), 1);
	}
	///writes a char 
	void	writeChar(int value){
		writeBits(value, 8);
	}
	///writes a byte (usually same as char)
	void	writeByte(int value){
		writeBits(value, 8);
	}
	///writes a short
	void	writeShort(int value){
		writeBits(value, 16);
	}
	///writes a long
	void	writeLong(int value){
		writeBits(value, 32);
	}
	///writes a float (this is by default 32 bit)
	void	writeFloat(float value){
		writeBits(*(int *)&value, 32);
	}
	///writes a string (which can be easily read using readString())
	void	writeString(const std::string &str){
		if(!str.length())return;
		const char *string=str.c_str();
		while (*string){
			writeBits(*string, 8);
			string++;
		}
		writeBits(0, 8);
	}

	///writes any type of data into the buffer
	void	writeData(const void *data, unsigned int size){
		int		i = 0;

		while (size >= 4){
			writeBits(((const unsigned *)data)[i++], 32);
			size -= 4;
		}
		i <<= 2;
		while (size){
			writeBits(((const byte *)data)[i++], 8);
			size--;
		}
	}

	int		readBits(int bits){
		int		read=0, count=0;
		int		shift = 0;
		int		value = 0;

		if (bits < 1 || bits > 32)
			throw BadArgumentException();

		if (bytePos*8+bitPos + bits > bitCount)
			throw OverflowException();

		// Read the bits
		while (bits){
			if (bitPos == 0){
				if (bits >= 8){
					read=data[bytePos++]&0xff;
					value |= read<<shift;
					shift += 8;
					bits -= 8;
					continue;
				}
				bytePos++;
			}

			count = 8 - bitPos;
			if (count > bits)
				count = bits;
			value |= (data[bytePos - 1]&0xff >> bitPos) & (BIT(count) - 1) << shift;
			bitPos = (bitPos + count) & 7;
			shift += count;
			bits -= count;
		}

		return value;
	}
	bool	readBool(){
		return (readBits(1))?true:false;
	}
	int		readChar(){
		return (char)readBits(8);
	}
	int		readByte(){
		return (byte)readBits(8);
	}
	int		readShort(){
		return (short)readBits(16);
	}
	int		readLong(){
		return readBits(32);
	}
	float	readFloat(){
		float	value;
		*(int*)&value=readBits(32);
		return value;
	}
	std::string	readString(){
		int		c, len = 0;
		std::string str;
		while (1){
			c = (byte)readBits(8);

			if (c <= 0 || c >= 255)
				break;

			// Translate all fmt spec to avoid crash bugs
			if (c == '%')
				c = '.';

			// Don't allow higher ASCII values
			if (c > 127)
				c = '.';

			str+=c;
		}
		return str;
	}
	int		readData(void *data, int size){
		int		count = bitCount;
		int		i = 0;

		while (size >= 4){
			((unsigned *)data)[i++] = (unsigned)readBits(32);
			size -= 4;
		}
		i <<= 2;
		while (size){
			((byte *)data)[i++] = (byte)readBits(8);
			size--;
		}
		//return (this-readCount - count) >> 3;
		return 0;
	}
private:
	///size of the buffer
	size_t	_size;
	///buffer where all data is stored (allocated on the stack)
	char 	*data;
	///current byte that is being written
	int		bytePos;
	///current bit inside the byte
	int		bitPos;
	///total number of bits in the buffer
	int		bitCount;
};